package com.server;

import com.Utils;
import com.data.Settings;
import com.data.Wheel;
import com.data.WheelManager;
import com.fb.FacebookSender;
import com.fb.IpCameraVideoGrabber;
import com.gooddies.events.Lookup;
import com.gui.mainwindow.MainWindow;
import com.music.MusicManager;
import javazoom.jlgui.basicplayer.BasicPlayerException;

import javax.mail.MessagingException;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.Socket;
import java.net.SocketException;

/**
 * @author sad
 */
public class Client {

    private volatile Socket clientSocket;
    private volatile DataInputStream inputStream;
    private OutputStream outputStream;
    private final static int PICTURE_COMMAND = 1;
    private final static int WHEEL_COMMAND = 2;
    private final static int MUSIC_COMMAND = 3;
    private final static int NOPE_COMMAND = 4;
    private final static int CLEAR_COMMAND = 9;
    private final static int MUTE_COMMAND = 10;
    private final static int UNMUTE_COMMAND = 11;
    private final static int STOP_MUSIC_COMMAND = 5;
    private final static int START_REC_COMMAND = 6;
    private final static int STOP_REC_COMMAND = 7;
    private final static int PLAY_EFFECT_COMMAND = 8;
    private final static int END_MARKER = 0xFAAFABBA;
    private final Server server;
    private volatile boolean isReadyToReceivePicture = true;
    private volatile boolean hasDataToSent = false;
    private final byte[] sendBuffer = new byte[78 * 39 * 3];

    private final BufferedImage lastImage = Utils.createOffscreenImage();
    private IpCameraVideoGrabber grabber;

    public Client(Socket socket, Server server) {
        this.server = server;
        clientSocket = socket;
        clientSocket = socket;
        Utils.clearImage(lastImage);//Utils.showImageDebug(lastImage);
    }

    public boolean isReadyToReceivePicture() {
        return isReadyToReceivePicture;
    }

    public void sendPicture(byte[] array) {
        isReadyToReceivePicture = false;
        System.arraycopy(array, 0, sendBuffer, 0, array.length);
        hasDataToSent = true;
    }

    public void start() throws IOException {

        processClearCommand();

        inputStream = new DataInputStream(new BufferedInputStream(clientSocket.getInputStream()));
        outputStream = new BufferedOutputStream(clientSocket.getOutputStream());
        Thread clientReadThread = new Thread() {
            @Override
            public void run() {
                readCycle();
            }
        };
        clientReadThread.start();
        Thread writeReadThread = new Thread() {
            @Override
            public void run() {
                writeCycle();
            }
        };
        writeReadThread.setPriority(Thread.MIN_PRIORITY);
        writeReadThread.start();
        Lookup.getDefault().fire("clientstatus", true);
    }

    private void println(String log) {
        System.err.println(log);
        System.out.println(log);

    }

    private static void skipWrongPacket(InputStream stream) {
        int[] endMark = new int[]{0xFA, 0xAF, 0xAB, 0xBA};
        int index = 0;
        while (true) {
            try {
                int b = stream.read();
                if (b == -1) {
                    return;
                }

                if (endMark[index] == b) {
                    index++;
                    if (index == endMark.length) {
                        break;
                    }
                } else {
                    index = 0;
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    private int readIntFromStream(InputStream stream) throws IOException {
        byte[] buffer = new byte[4];
        for (int i = 0; i < 4; i++) {
            int result = stream.read();
            if (result == -1) {
                throw new SocketException("Cannot read from socket");
            }
            buffer[i] = (byte) result;
        }
        int value = Utils.toInt(buffer, 0);
        return value;
    }

    private byte readByteFromInputStream(InputStream stream) throws IOException {
        int result = stream.read();
        if (result == -1) {
            throw new SocketException("Cannot read from socket");
        }
        return (byte) result;
    }

    int counter = 0;
    long lastMillisecconds = System.currentTimeMillis();

    private void writeCycle() {
        while (server.isUp() && outputStream != null && clientSocket != null) {
            if (!hasDataToSent) {
                try {
                    Thread.sleep(50);
                    continue;
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }

            try {
                SendPacket packet = new SendPacket(PICTURE_COMMAND);
                packet.putByte((byte) 2);//layer
                packet.putByteBuffer(sendBuffer);
                packet.sendAndFlush(outputStream);
                hasDataToSent = false;
                isReadyToReceivePicture = true;
                counter++;
                if ((System.currentTimeMillis() - lastMillisecconds) > 1000) {
                    System.out.println("Sent pictures=" + counter);
                    lastMillisecconds = System.currentTimeMillis();
                    counter = 0;
                }
            } catch (Throwable ex) {
                println("Exception. ReadCycle " + ex.getMessage());
                ex.printStackTrace();
                tryToCloseClient();
            }
        }
    }

    private void readCycle() {
        while (server.isUp() && inputStream != null && !clientSocket.isClosed()) {
            try {
                int size = readIntFromStream(inputStream);
                //System.out.print("\nReceived " + size);

                int nsize = size - 5;
                if (nsize < 0) {
                    System.out.print(" negative size packet " + nsize);
                    continue;
                }
                byte[] buffer = new byte[size - 5];
                // System.out.print(" buffer size=" + buffer.length);
                int command = readByteFromInputStream(inputStream);
                // System.out.print(" buffer length= " + buffer.length);
                inputStream.readFully(buffer);
                int endMarker = readIntFromStream(inputStream);
                // System.out.print(" end marker=" + endMarker + ". Date " + new Date());
                if (endMarker != END_MARKER) {
                    System.out.println("Received bad packet. End marker is not equals to " + END_MARKER + " but equals " + endMarker + "!!!!!!!");
                    skipWrongPacket(inputStream);
                    continue;
                }
                switch (command) {
                    case PICTURE_COMMAND:
                        processPictureCommand(buffer);
                        break;
                    case WHEEL_COMMAND:
                        processWheelCommand(buffer);
                        break;
                    case CLEAR_COMMAND:
                        processClearCommand();
                        break;
                    case PLAY_EFFECT_COMMAND:
                        processEffectCommand(buffer);
                        break;
                    case MUSIC_COMMAND:
                        processMusicCommand(buffer);
                        break;
                    case STOP_MUSIC_COMMAND:
                        processMusicStop();
                        break;
                    case START_REC_COMMAND:
                        processStartRecCommand();
                        break;
                    case STOP_REC_COMMAND:
                        processRecFinishCommand(buffer);
                        break;
                    case MUTE_COMMAND:
                        processMuteCommand();
                        break;
                    case UNMUTE_COMMAND:
                        processUnmuteCommand();
                        break;
                    case NOPE_COMMAND:
                        //do nothing
                        break;
                    default:
                        println("Unrecognized command from client " + command);
                }
            } catch (SocketException ex) {
                tryToCloseClient();
            } catch (Exception ex) {
                ex.printStackTrace();
                println("Exception. ReadCycle " + ex.getMessage());
            } catch (Error error) {
                error.printStackTrace();
                tryToCloseClient();
            }
        }
    }

    public void tryToCloseClient() {
        System.out.println("Client disconnected");
        //forceCameraGrabberStop();
        WheelManager.getInstace().stopWheels();
        try {
            if (inputStream != null) {
                inputStream.close();
                inputStream = null;
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        try {
            if (clientSocket != null) {
                try {
                    clientSocket.shutdownInput();
                } catch (Exception ex) {
                }

                try {
                    clientSocket.shutdownOutput();
                } catch (Exception ex) {
                }

                clientSocket.close();
                clientSocket = null;
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            Lookup.getDefault().fire("clientstatus", false);
        }
    }

    public BufferedImage getLastImage() {
        return lastImage;
    }

    private void processPictureCommand(byte[] buffer) {
        Utils.copyBufferToImage(buffer, lastImage, 1);//Utils.showImageDebug(lastImage);
        BufferedImage resultImage = MainWindow.getInstance().combineLayers(lastImage);//Utils.showImageDebug(resultImage);
        Utils.copyImageToBuffer(resultImage, buffer);
        server.getArtNetProcessor().sendPicture(buffer);
    }

    private void processWheelCommand(byte[] buffer) {
        int entriesCount = Utils.lhTo16Bit(buffer[1], buffer[0]);
        int index = 2;
        for (int i = 0; i < entriesCount; i++, index += 3) {
            int wheelId = Utils.lhTo16Bit(buffer[index + 1], buffer[index]);
            byte speed = buffer[index + 2];
            Wheel wheel = WheelManager.getInstace().getWheel(wheelId);
            if (wheel != null) {
                wheel.setExpectedSpeed(speed);
            }
        }

        MainWindow.getInstance().startWheelTimer();
    }

    private void processMusicStop() {
        System.out.println("Music stop");
        MusicManager.getInstance().stopMusicPlayedById();
        MainWindow.getInstance().stopWheelTimer();
    }

    private void processMusicCommand(byte[] buffer) {
        int entriesCount = Utils.lhTo16Bit(buffer[1], buffer[0]);
        String musicId = new String(buffer, 2, entriesCount);

        boolean visual = buffer[2 + entriesCount] != 0;
        System.out.println("Music " + musicId + " visual");
        MainWindow.getInstance().stopWheelTimer();
        try {
            MusicManager.getInstance().stopMusicPlayedById();
            System.out.println("Play music  =  "+ musicId);

            MusicManager.getInstance().playById(musicId, visual);
        } catch (BasicPlayerException ex) {
            ex.printStackTrace();
        }
    }

    private void processEffectCommand(byte[] buffer) {
        System.out.println("Effect");
        int entriesCount = Utils.lhTo16Bit(buffer[1], buffer[0]);
        String musicId = new String(buffer, 2, entriesCount);
        try {
            System.out.println("Play effect =  "+ musicId);
            MusicManager.getInstance().playEffect(musicId);
        } catch (BasicPlayerException ex) {
            ex.printStackTrace();
        }
    }

    public void processClearCommand() {
        System.out.println("Clear command");
        MusicManager.getInstance().stopMusicPlayedById();
        Utils.clearImage(lastImage);
        MainWindow.getInstance().clear();
        MainWindow.getInstance().stopWheelTimer();
    }

    private void processMuteCommand() {
        System.out.println("Muted");
        MusicManager.getInstance().mute();
    }

    private void processUnmuteCommand() {
        System.out.println("Unmuted");
        MusicManager.getInstance().unmute();
    }

    private void forceCameraGrabberStop() {
        if (grabber != null) {
            grabber.forceEnd();
        }
    }

    private void processStartRecCommand() {
        forceCameraGrabberStop();
        Settings settings = MainWindow.getInstance().getSettings();
        String facebookLogin = settings.getFaceBookEmail();
        String facebookPassword = settings.getFaceBookPassword();
        String appId = settings.getFacebookAppId();
        String appSecret = settings.getFacebookAppSecret();
        String cameraUrl = settings.getCameraUrl();
        String cameraUser = settings.getCameraUser();
        String cameraPassword = settings.getCameraPassword();
        File directory = new File("./video");
        if (!directory.exists()) {
            directory.mkdirs();
        }

        grabber = new IpCameraVideoGrabber(new FacebookSender(facebookLogin, facebookPassword, appId, appSecret), new IpCameraVideoGrabber.OnEndEvent() {

            @Override
            public void end() {
                grabber = null;
            }

            @Override
            public void videoUploaded(String facebookUrl, String userEmail) {
                onVideoUploaded(userEmail, facebookUrl);
            }
        });

        grabber.startWriting(cameraUrl, cameraUser, cameraPassword, directory, settings.getCameraFrameRate());
    }

    public void onVideoUploaded(String email, String facebookVideoUrl) {
        Settings settings = MainWindow.getInstance().getSettings();
        MailSender sender = new MailSender(settings.getMailServer(), settings.getMailAddress(), settings.getMailPassword());


        String subject = "Autoreply";

        try {
            sender.sendMesssage(subject, getContent(facebookVideoUrl), email);
        } catch (MessagingException ex) {
            ex.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void processRecFinishCommand(byte[] buffer) {
        System.out.println("STOP RECORDING");
        int entriesCount = Utils.lhTo16Bit(buffer[1], buffer[0]);
        String email = new String(buffer, 2, entriesCount);
        System.out.println("STOP RECORDING1");

        if (grabber != null) {
            System.out.println("STOP RECORDING3");

            grabber.endGrabbing(email);
        }
    }

    public static MimeMultipart getContent(String url) throws MessagingException, IOException {
        MimeMultipart mimeMultipart = new MimeMultipart("related");
        String urlTag = "<a href=\"" + url + "\"> " + url + "</a>";

        String text = "<span style=\"font-family:&quot;Arial&quot;,&quot;sans-serif&quot;\">Dear Sir!</span> <br><br><span\n" +
                "        style=\"font-family:&quot;Arial&quot;,&quot;sans-serif&quot;\">Thank you for attending KLW's booth at Innotrans!</span>\n" +
                "<br><br><span style=\"font-family:&quot;Arial&quot;,&quot;sans-serif&quot;\">You may download your masterpiece via the link: </span>\n" +
                urlTag +
                "<br><br><span style=\"font-family:&quot;Arial&quot;,&quot;sans-serif&quot;\">To learn more about KLW, visit </span><a\n" +
                "        href=\"http://www.klw.biz/en/\" target=\"_blank\"><span style=\"font-family:&quot;Arial&quot;,&quot;sans-serif&quot;\">www.klw.biz</span></a>\n" +
                "<br><br><br><br><br><br><br><span\n" +
                "        style=\"font-size:10pt;font-family:&quot;Microsoft Sans Serif&quot;,&quot;sans-serif&quot;\">Best regards,<br>Anastasiya A. Kovaleva,</span>\n" +
                "<br><span style=\"font-size:10pt;font-family:&quot;Microsoft Sans Serif&quot;,&quot;sans-serif&quot;\">Marketing communication specialist<br><br></span>\n" +
                "<img src=\"cid:img1\"" +
                "     border=\"0\" height=\"108\" width=\"85\"><span\n" +
                "        style=\"font-size:10pt;font-family:&quot;Microsoft Sans Serif&quot;,&quot;sans-serif&quot;\"><br>Corporate Affairs Department</span>\n" +
                "<br><span style=\"font-size:10pt;font-family:&quot;Microsoft Sans Serif&quot;,&quot;sans-serif&quot;\">Phone: <a\n" +
                "        href=\"tel:%2B380%20562%20389%20569\" value=\"+380562389569\" target=\"_blank\">+380 562 389 569</a></span> <br><a\n" +
                "        href=\"mailto:Anastasiya.Kovaleva@interpipe.biz\" target=\"_blank\"><span\n" +
                "        style=\"font-size:10pt;font-family:&quot;Microsoft Sans Serif&quot;,&quot;sans-serif&quot;\">Anastasiya.Kovaleva@interpipe.<wbr>biz</span></a>\n" +
                "<u><span style=\"font-size:10pt;font-family:&quot;Microsoft Sans Serif&quot;,&quot;sans-serif&quot;\"><br></span></u><a\n" +
                "        href=\"http://www.interpipe.biz/en/\" target=\"_blank\"><span\n" +
                "        style=\"font-size:10pt;font-family:&quot;Microsoft Sans Serif&quot;,&quot;sans-serif&quot;\">www.interpipe.biz</span></a>\n" +
                "<span style=\"font-size:10pt;font-family:&quot;Microsoft Sans Serif&quot;,&quot;sans-serif&quot;\"> </span><u></u><u></u>";

        MimeBodyPart textPart = new MimeBodyPart();

        textPart.setContent(text, "text/html");

        MimeBodyPart imagePart = new MimeBodyPart();
        imagePart.attachFile("./pics/interpipe.gif");
        imagePart.setContentID("<img1>");
        imagePart.setDisposition(MimeBodyPart.INLINE);

        mimeMultipart.addBodyPart(textPart);
        mimeMultipart.addBodyPart(imagePart);
        return mimeMultipart;

    }

}
